package com.example.demo.aspect;

import com.example.demo.anotation.ControllerLog;
import com.example.demo.utils.IPUtils;
import com.example.demo.utils.JacksonUtils;
import com.example.demo.utils.R;
import com.example.demo.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.time.Instant;
import java.util.Map;


/**
 * Controller 日志记录
 *
 * @author wangxiaolei
 * @date 2020/5/7 16:06
 */
@Component
@Aspect
public class ControllerLogAspect {
    private static final Logger log = LoggerFactory.getLogger(ControllerLogAspect.class);



    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        Class<?> clazz = point.getTarget().getClass();
        Object[] args = point.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                //ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
                //ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException: getOutputStream() has already been called for this response
                continue;
            }
            arguments[i] = args[i];
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String value = "";
        String desc = "";
        ControllerLog controllerLog = method.getAnnotation(ControllerLog.class);
        if (controllerLog != null) {
            value = controllerLog.value();
            desc = controllerLog.desc();
        }

        String requestType = "POST";
        String url = "";
        StringBuilder reqeustParam = new StringBuilder();
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            requestType = request.getMethod();
            url = request.getRequestURL().toString();
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (request.getQueryString() != null) {
                try {
                    reqeustParam.append(URLDecoder.decode(request.getQueryString(), "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            } else {
                //针对post请求
                for (String k : parameterMap.keySet()) {
                    String[] values = parameterMap.get(k);
                    for (int i = 0; i < values.length; i++) {
                        String v = values[i];
                        reqeustParam.append(k).append("=").append(v).append("&");
                    }
                }
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }

        long s = Instant.now().toEpochMilli();
        Object proceed = point.proceed();
        long e = Instant.now().toEpochMilli();
        try {
            String reqIp = IPUtils.getIpAddr();
            String reqBody = JacksonUtils.obj2json(arguments);
            log.info("\n---------------------- Controller log-> ----------------------" +
                    "\nlog->ip            : " +  reqIp +
                    "\nlog->url           : " + url +
                    "\nlog->request_type  : " + requestType +
                    "\nlog->request_param : " + reqeustParam.toString() +
                    "\nlog->request_body  : " +  reqBody+
                    "\nlog->method        : " + clazz.getCanonicalName() + "." + method.getName() +
                    "\nlog->value         : " + value +
                    "\nlog->desc          : " + desc +
                    "\nlog->duration      : " + (e - s) / 1000.0 + "s" +
                    "\n-------------------------log->--------------------------------");

            if (proceed instanceof R){
                if(((R) proceed).getElapsedSecond() == null){
                    ((R) proceed).setElapsedSecond((e - s) / 1000.0);
                }
            }

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return proceed;
    }
}
